package com.study.service.impl;

import com.alibaba.fastjson.JSON;
import com.study.account.AddressInfo;
import com.study.account.BalanceInfo;
import com.study.common.TableId;
import com.study.dao.EcommerceOrderDao;
import com.study.entity.EcommerceOrder;
import com.study.feign.AddresClient;
import com.study.feign.NotSecureBalanceClient;
import com.study.feign.NotSecureGoodsClient;
import com.study.feign.SecureGoodsClient;
import com.study.filter.AccessContext;
import com.study.goods.DeductGoodsInventory;
import com.study.goods.SimpleGoodsInfo;
import com.study.service.IOrderService;
import com.study.source.LogisticSource;
import com.study.vo.PageSimpleOrderDetail;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import order.LogisticMessage;
import order.OrderInfo;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service
@EnableBinding(LogisticSource.class)
public class IOrderServiceImpl implements IOrderService {

    //表接口
    @Resource
    private EcommerceOrderDao ecommerceOrderDao;

    //feign 客户端
    @Resource
    private AddresClient addresClient;
    @Resource
    private SecureGoodsClient secureGoodsClient;
    @Resource
    private NotSecureGoodsClient notSecureGoodsClient;
    @Resource
    private NotSecureBalanceClient notSecureBalanceClient;

    //springcloud stream 发射器
    @Resource
    private LogisticSource logisticSource;

    public IOrderServiceImpl(EcommerceOrderDao ecommerceOrderDao,
                             AddresClient addresClient,
                             SecureGoodsClient secureGoodsClient,
                             NotSecureGoodsClient notSecureGoodsClient,
                             NotSecureBalanceClient notSecureBalanceClient,
                             LogisticSource logisticSource) {
        this.ecommerceOrderDao = ecommerceOrderDao;
        this.addresClient = addresClient;
        this.secureGoodsClient = secureGoodsClient;
        this.notSecureGoodsClient = notSecureGoodsClient;
        this.notSecureBalanceClient = notSecureBalanceClient;
        this.logisticSource = logisticSource;
    }

    /**
     * 创建订单
     * 当不满足时候、直接抛出异常；
     * 1、校验请求对象是否合法
     * 2、创建订单
     * 3、扣减商品库存
     * 4、扣减用户余额
     * 5、发送订单消息 springcloud stream + kafka
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public TableId createOrder(OrderInfo orderInfo) {

        //获取地址信息
        AddressInfo addressInfo = addresClient.getAddressInfoByTableId(
                new TableId(Collections.singletonList(
                        new TableId.Id(orderInfo.getUserAddressId())
                ))
        ).getData();

        //1、校验请求对象是否合法
        if (CollectionUtils.isEmpty(addressInfo.getAddressItems())) {
            throw new RuntimeException("用户地址不存在：" + orderInfo.getUserAddressId());
        }

        //2、创建订单
        EcommerceOrder newOrder = ecommerceOrderDao.save(
                new EcommerceOrder(
                        AccessContext.getLoginUserInfo().getId(),
                        orderInfo.getUserAddressId(),
                        JSON.toJSONString(orderInfo.getOrderItems())
                )
        );

        log.info("创建订单成功：[{}],[{}]",
                AccessContext.getLoginUserInfo().getId(), newOrder.getId());

        //3、扣减商品库存
        if (
                !notSecureGoodsClient.deductGoodsInventory(
                        orderInfo.getOrderItems().stream()
                                .map(OrderInfo.OrderItem::toDeductGoodsInventory)
                                .collect(Collectors.toList())
                ).getData()
                ) {
            throw new RuntimeException("扣减库存失败");
        }

        //4、扣减用户余额
        //4.1获取商品信息、计算总价格
        List<SimpleGoodsInfo> goodsInfos = notSecureGoodsClient.getSimpleGoodsInfoByTableId(
                new TableId(
                        orderInfo.getOrderItems().stream()
                                .map(o -> new TableId.Id(o.getGoodsId()))
                                .collect(Collectors.toList())
                )
        ).getData();
        //list转Map
        Map<Long, SimpleGoodsInfo> goodsId2GoodsInfo = goodsInfos.stream()
                .collect(Collectors
                        .toMap(SimpleGoodsInfo::getId, Function.identity()));

        BigDecimal balance = new BigDecimal("0.00");
        for (OrderInfo.OrderItem orderItem : orderInfo.getOrderItems()) {
            balance = balance.add(goodsId2GoodsInfo.get(orderItem.getGoodsId()).getPrice()
                    .multiply(new BigDecimal(orderItem.getCount())));

        }

        if (balance.compareTo(BigDecimal.ZERO) == -1
                || balance.compareTo(BigDecimal.ZERO) == 0) {
            throw new RuntimeException("商品总价小于0！");
        }

        //4.2扣减账号余额
        BalanceInfo balanceInfo = notSecureBalanceClient.deductBalance(
                new BalanceInfo(AccessContext.getLoginUserInfo().getId(), balance)
        ).getData();

        if (null == balanceInfo) {
            throw new RuntimeException("扣减余额失败！");
        }

        log.info("扣减用户余额：[{}],[{}]", newOrder.getId(),
                JSON.toJSONString(balanceInfo));

        //5、发送订单消息 springcloud stream + kafka
        LogisticMessage logisticMessage = new LogisticMessage(
                AccessContext.getLoginUserInfo().getId(),
                newOrder.getId(),
                orderInfo.getUserAddressId(),
                null //没有备注
        );

        if (!logisticSource.logisticsOutput().send(
                MessageBuilder.withPayload(JSON.toJSONString(
                        logisticMessage
                )).build()
        )) {
            throw new RuntimeException("发送 logistics message 失败！");
        }

        return new TableId(Collections.singletonList(new TableId.Id(newOrder.getId())));
    }

    @Override
    public PageSimpleOrderDetail getSimpleOrderDetailByPage(int page) {

        if (page <= 0) {
            page = 1;
        }

        //分页规则：1页10条、按照id倒序排序
        org.springframework.data.domain.Pageable pageable = PageRequest.of(
                page-1,10, Sort.by("id").descending());

        Page<EcommerceOrder> orderPage = ecommerceOrderDao.findAllByUserId(
                AccessContext.getLoginUserInfo().getId(),pageable);

        List<EcommerceOrder> orders = orderPage.getContent();

        if(CollectionUtils.isEmpty(orders)){
            return new PageSimpleOrderDetail(Collections.emptyList(),false);
        }

        Set<Long> goodsIdsInOrders = new HashSet<>();
        orders.forEach(o -> {

            List<DeductGoodsInventory> goodsAndCount = JSON.parseArray(
                    o.getOrderDetail(), DeductGoodsInventory.class);

            goodsIdsInOrders.addAll(goodsAndCount.stream()
                .map(DeductGoodsInventory::getGoodsId)
                .collect(Collectors.toSet()));

        });

        assert  !CollectionUtils.isEmpty(goodsIdsInOrders);

        //是否还有更多页
        boolean hasMore = orderPage.getTotalPages() > page;

        //获取商品信息
        List<SimpleGoodsInfo> goodsInfos = secureGoodsClient.getSimpleGoodsInfoByTableId(
                new TableId(goodsIdsInOrders.stream()
                        .map(TableId.Id::new).collect(Collectors.toList()))

        ).getData();

        //获取地址信息
        AddressInfo addressInfo = addresClient.getAddressInfoByTableId(
                new TableId(orders.stream()
                    .map(o -> new TableId.Id(o.getAddressId()))
                    .distinct().collect(Collectors.toList()))
        ).getData();

        //组装订单中的商品、地址信息-》订单信息
        return new PageSimpleOrderDetail(
                assembleSimpleOrderDetail(orders,goodsInfos,addressInfo),
                hasMore
        );
    }

    /**
     * 组装订单详情
     */
    private List<PageSimpleOrderDetail.SingleOrderItem> assembleSimpleOrderDetail(
            List<EcommerceOrder> orders,List<SimpleGoodsInfo> goodsInfos,
            AddressInfo addressInfo
    ){
        //List -> Map
        Map<Long,SimpleGoodsInfo> id2GoodsInfo = goodsInfos.stream()
                .collect(Collectors.toMap(SimpleGoodsInfo::getId,Function.identity()));
        //List -> Map
        Map<Long,AddressInfo.AddressItem> id2AddressItem = addressInfo.getAddressItems()
                .stream().collect(
                        Collectors.toMap(AddressInfo.AddressItem::getId,Function.identity())
                );

        List<PageSimpleOrderDetail.SingleOrderItem> result = new ArrayList<>();

        orders.forEach(o -> {

            PageSimpleOrderDetail.SingleOrderItem  orderItem =
                    new PageSimpleOrderDetail.SingleOrderItem();
            orderItem.setId(o.getId());

            orderItem.setUserAddress(id2AddressItem.getOrDefault(o.getAddressId(),
                    new AddressInfo.AddressItem(-1L)).toUserAddress());
            orderItem.setGoodsItems(buildOrderGoodsItem(o,id2GoodsInfo));

            result.add(orderItem);
        });

        return result;
    }

    //构造订单中的商品信息
    private List<PageSimpleOrderDetail.SingleOrderGoodsItem> buildOrderGoodsItem(
            EcommerceOrder order,Map<Long,SimpleGoodsInfo> id2GoodsInfo
    ){
        List<PageSimpleOrderDetail.SingleOrderGoodsItem> goodsItems = new ArrayList<>();
        List<DeductGoodsInventory> goodsAndCount = JSON.parseArray(
                order.getOrderDetail(),DeductGoodsInventory.class);

        goodsAndCount.forEach(gc -> {
            PageSimpleOrderDetail.SingleOrderGoodsItem goodsItem =
                    new PageSimpleOrderDetail.SingleOrderGoodsItem();
            goodsItem.setCount(gc.getCount());
            goodsItem.setSimpleGoodsInfo(id2GoodsInfo.getOrDefault(
                    gc.getGoodsId(),new SimpleGoodsInfo(-1L)));

            goodsItems.add(goodsItem);
        });
        return goodsItems;
    }

}
